home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 5 / Apprentice-Release5.iso / Source Code / C++ / Applications / PICSee Dust 1.01 / Quaternary Source / SoundUtils.c < prev    next >
Text File  |  1995-11-23  |  19KB  |  673 lines

  1. /*
  2.     File: SoundUtils.c
  3.     Written by Hiep Dam.
  4.  
  5.     Based on code written by Brigham Stevens, develop 17 (GameSounds.c)
  6.     Herein lies the "core" sound routines generic enuf to be usable by anyone
  7.     and any application.
  8.  
  9.     Last Update: Oct 1995
  10.     
  11.     Version 1.1 (Oct 26 95) [HTD]: Compiled with PowerPC, updated CreateSndChannel
  12.         to be compatible with PPC via call to NewSndCallBackProc & addition of
  13.         global UPP sSndCallBackProc.
  14.  
  15. */
  16.  
  17. #include <Sound.h>
  18. #include "SoundUtils.h"
  19.  
  20.  
  21. enum {
  22.     kSndIdle = 0,        // No sound is playing, and sound has been purged
  23.     kSndDone = -1        // Sound done playing, and needs to be unlocked & purged
  24. };
  25.  
  26. // Our private data structure, used to hold the sound channel,
  27. // as well as other info pertaining to the channel
  28. typedef struct {
  29.     SndChannelPtr chan;
  30.     Handle snd;
  31.     short priority;            // Either kSndIdle, kSndDone, or a number > 0
  32.     short padding;
  33. } SndChanInfo, *SndChanInfoPtr;
  34.  
  35. // Our global storage of channel data (4 max)
  36. static SndChanInfo gSndChans[kMaxChans] =
  37.     {    nil, nil, kSndIdle, 0,
  38.         nil, nil, kSndIdle, 0,
  39.         nil, nil, kSndIdle, 0,
  40.         nil, nil, kSndIdle, 0 };
  41.  
  42. // Sound Manager Version miscellany
  43. #define kEnhancedSoundMgr 3
  44. static unsigned char gSoundMgrVersion;
  45. static char padding_char;
  46. static short padding_short;
  47. static SndCallBackUPP sSndCallBackProc = NULL;
  48.  
  49. //#define REINIT_CHANNEL
  50.  
  51. // ---------------------------------------------------------------------------
  52.  
  53. // Our callback procedure.
  54. pascal void SndDoneProc(SndChannelPtr chan, SndCommand *cmd);
  55.  
  56. // ---------------------------------------------------------------------------
  57.  
  58. void InitSoundUtils() {
  59. /*
  60.     This sets up some internal variables; you should call this
  61.     before using any other calls in SoundUtils.c, else some
  62.     strange things might happen...
  63. */
  64.     //gSoundMgrVersion = SndSoundManagerVersion().majorRev;
  65.     gSoundMgrVersion = SndSoundManagerVersion();
  66.  
  67.     sSndCallBackProc = NewSndCallBackProc(SndDoneProc);
  68. } // END InitSoundUtils
  69.  
  70. // ---------------------------------------------------------------------------
  71.  
  72. OSErr CreateSndChannel(short whichChan) {
  73. /*
  74.     Create a sound channel. If the channel whichChan is currently
  75.     occupied, it disposes it first before allocating a new channel.
  76. */
  77.     OSErr err;
  78.     if (gSndChans[whichChan].chan != nil)
  79.         err = DisposeSndChannel(whichChan);
  80.  
  81.     // Ah, for the love of PPC!
  82.     // If the snd callback proc hasn't been assigned yet, do it now
  83.     if (sSndCallBackProc == NULL)
  84.         sSndCallBackProc = NewSndCallBackProc(SndDoneProc);
  85.  
  86.     err = SndNewChannel(&gSndChans[whichChan].chan, sampledSynth,
  87.         initMono + initNoDrop + initNoInterp, sSndCallBackProc);
  88.  
  89.     return(err);
  90. } // END CreateSndChannel
  91.  
  92. // ---------------------------------------------------------------------------
  93.  
  94. OSErr DisposeSndChannel(short whichChan) {
  95. /*
  96.     Dispose a sound channel and resets some internal data.
  97.     If the channel is already disposed, it just exits.
  98. */
  99.     OSErr err;
  100.  
  101.     if (gSndChans[whichChan].chan == nil)
  102.         return(noErr);
  103.     
  104.     err = SndDisposeChannel(gSndChans[whichChan].chan, true);
  105.     // Reset our internal data, flags...
  106.     if (err == noErr) {
  107.         gSndChans[whichChan].chan = nil;
  108.         gSndChans[whichChan].snd = nil;
  109.         gSndChans[whichChan].priority = kSndIdle;
  110.     }
  111.     
  112.     return(err);
  113. } // END DisposeSndChannel
  114.  
  115. // ---------------------------------------------------------------------------
  116.  
  117. void PlayAsynch(Handle sndHdl, short whichChan) {
  118. /*
  119.     Play a sound asynchronously, using SndPlay.
  120.  
  121.     NOTE: The channel specified in whichChan must be
  122.     already allocated via CreateSndChannel(). No checking
  123.     is done.
  124. */
  125.     OSErr err;
  126.  
  127. #ifdef REINIT_CHANNEL
  128.     if (gSoundMgrVersion < kEnhancedSoundMgr) {
  129.         err = DisposeSndChannel(whichChan);
  130.         if (err != noErr)
  131.             return;
  132.         err = CreateSndChannel(whichChan);
  133.         if (err != noErr)
  134.             return;
  135.     }
  136. #endif
  137.  
  138.     HLock(sndHdl);
  139.     err = SndPlay(gSndChans[whichChan].chan, (SndListHandle)sndHdl, true); // Simple, no?
  140. } // END PlayAsynch
  141.  
  142. // ---------------------------------------------------------------------------
  143.  
  144. void PlayAsynchBuffer(Handle sndHdl, short whichChan) {
  145. /*
  146.     This routine plays a sound asynchronously, using
  147.     bufferCmds and SndDoImmediate, rather than SndPlay.
  148. */
  149.     SoundHeader *sndDataOffset;
  150.     long offset;
  151.     SndCommand sndCmd;
  152.     OSErr err;
  153.  
  154.     // First, lock the sucker.
  155.     if (sndHdl == nil) return;
  156.     HLockHi(sndHdl);
  157.  
  158. #ifdef REINIT_CHANNEL
  159.     if (gSoundMgrVersion < kEnhancedSoundMgr) {
  160.         err = DisposeSndChannel(whichChan);
  161.         if (err != noErr) {
  162.             HUnlock(sndHdl); return;
  163.         }
  164.         err = CreateSndChannel(whichChan);
  165.         if (err != noErr) {
  166.             HUnlock(sndHdl); return;
  167.         }
  168.     }
  169. #endif
  170.  
  171.     // Get the offset into the sound resource, wherein the
  172.     // sound header lies...
  173.     err = RetrieveSndHeaderOffset(sndHdl, &offset);
  174.     if (err != noErr) {
  175.         HUnlock(sndHdl);
  176.         return;
  177.     }
  178.  
  179.     // The bufferCmd requires we give it a pointer to a sound
  180.     // header, which lies somewhere in the sound resource.
  181.     // We retrieve this location by determining the offset into
  182.     // the resource via RetrieveSndHeaderOffset, add it to
  183.     // the beginning address of the sound, and voila!
  184.     sndDataOffset = (SoundHeader*)(StripAddress(*sndHdl) + offset);
  185.  
  186.     // Stuff in the parameters so bufferCmd can do its thing...
  187.     sndCmd.cmd = bufferCmd;
  188.     sndCmd.param1 = 0;
  189.     sndCmd.param2 = (long)sndDataOffset;
  190.  
  191.     // Push it into the sound channel queue...
  192.     err = SndDoCommand(gSndChans[whichChan].chan, &sndCmd, false);
  193. } // END PlayAsycnchBuffer
  194.  
  195. // ---------------------------------------------------------------------------
  196.  
  197. void SndChanStop(SndChannelPtr chan) {
  198. /*
  199.     Use this to stop any sound currently playing.
  200.     It does this by first telling the sound channel to
  201.     stop (quiet) and removes any other sounds that
  202.     may have been queued in the channel (flush), else
  203.     those might start playing...
  204. */
  205.     SndCommand sndCmd;
  206.     OSErr err;
  207.  
  208.     sndCmd.cmd = quietCmd;
  209.     sndCmd.param1 = sndCmd.param2 = 0;    // Unused
  210.     err = SndDoImmediate(chan, &sndCmd);
  211.     if (err == noErr) {
  212.         sndCmd.cmd = flushCmd;
  213.         err = SndDoImmediate(chan, &sndCmd);
  214.     }
  215. } // END SndChanStop
  216.  
  217. void SndStop(short whichChan) {
  218.     SndChanStop(gSndChans[whichChan].chan);
  219. } // END SndStop
  220.  
  221. // ---------------------------------------------------------------------------
  222.  
  223. void SndStopSoftly(short whichChan) {
  224.     if (!SndDone(whichChan)) {
  225.         short saveAmp, i;
  226.         long dummy;
  227.  
  228.         saveAmp = SndGetAmplitude(whichChan);
  229.  
  230.         i = saveAmp;
  231.         while (i > 0) {
  232.             SndSetAmplitude(whichChan, i);
  233.             i -= 10;
  234.             Delay(1, &dummy);
  235.             if (i < 0) break;
  236.         }
  237.  
  238.             // Stop it now.
  239.         SndStop(whichChan);
  240.             // Restore amplitude, since setting amplitude with no
  241.             // sound playing will apply it to next sound played...
  242.         SndSetAmplitude(whichChan, saveAmp);
  243.     }
  244. } // END SndStopSoftly
  245.  
  246. // ---------------------------------------------------------------------------
  247.  
  248. void WaitTillSndDone(short whichChan) {
  249.     while (!SndDone(whichChan)) {
  250.         // Do nothing
  251.     }
  252. } // END WaitTillSndDone
  253.  
  254. // ---------------------------------------------------------------------------
  255.  
  256. Boolean SndDone(short whichChan) {
  257. /*
  258.     Tells you whether the sound is done playing
  259.     (i.e. whether the sound channel is idle or not).
  260.     Specify which channel...
  261. */
  262.     return(SndChanDone(gSndChans[whichChan].chan));
  263. } // END SndDone
  264.  
  265. Boolean SndChanDone(SndChannelPtr chan) {
  266. /*
  267.     Same as SndDone, but you can pass any 'ol sound channel,
  268.     not just our private array of snd channels...
  269. */
  270.     OSErr err;
  271.     SCStatus status;
  272.  
  273.     // Poll the channel
  274.     err = SndChannelStatus(chan, sizeof(SCStatus), &status);
  275.     if (err == noErr)
  276.         // If the channel is busy, then the sound is NOT done,
  277.         // else the sound is done (and the channel is idle)...
  278.         return(!status.scChannelBusy);
  279.     else
  280.         return(false); // Hmm. Error!
  281. } // END SndChanDone
  282.  
  283. // ---------------------------------------------------------------------------
  284.  
  285. short SndChanGetAmplitude(SndChannelPtr chan) {
  286. /*
  287.     Get the amplitude of the sound currently being played
  288.     in chan. If an error occurred, will return -1, else
  289.     a value in the range 0..255
  290. */
  291.     SndCommand sndCmd;
  292.     OSErr err;
  293.     short amp;
  294.  
  295.     sndCmd.cmd = getAmpCmd;
  296.     sndCmd.param1 = 0;                // Unused
  297.     sndCmd.param2 = (long)&
  298.  
  299.     err = SndDoImmediate(chan, &sndCmd);
  300.     if (err != noErr)
  301.         return(-1);
  302.     else
  303.         return(amp);
  304. } // END SndChanGetAmplitude
  305.  
  306. short SndGetAmplitude(short whichChan) {
  307.     return(SndChanGetAmplitude(gSndChans[whichChan].chan));
  308. } // END SndGetAmplitude
  309.  
  310. // ---------------------------------------------------------------------------
  311.  
  312. void SndChanSetAmplitude(SndChannelPtr chan, short amp) {
  313. /*
  314.     Use this to change the amplitude (loudness) of the sound being
  315.     currently played in the sound channel. Note, if no sound
  316.     is playing this sets the amplitude of the next sound to
  317.     be played.
  318.     The amplitude range in "amp" should be in the range 0..255
  319. */
  320.     SndCommand sndCmd;
  321.     OSErr err;
  322.  
  323.     if (chan != nil) {
  324.         sndCmd.cmd = ampCmd;
  325.         sndCmd.param1 = amp;
  326.         sndCmd.param2 = 0;
  327.     }
  328.  
  329.     err = SndDoImmediate(chan, &sndCmd);
  330. } // END SndChanSetAmplitude
  331.  
  332. void SndSetAmplitude(short whichChan, short amp) {
  333.     SndChanSetAmplitude(gSndChans[whichChan].chan, amp);
  334. } // END SndSetAmplitude
  335.  
  336. // ---------------------------------------------------------------------------
  337.  
  338. // Format 1 of 'snd ' rsrc header
  339. typedef struct {
  340.     short format, numSynths;
  341. } Snd1Header, *Snd1HeaderPtr;
  342.  
  343. // Format 2 of 'snd ' rsrc header
  344. typedef struct {
  345.     short format, refCount;
  346. } Snd2Header, *Snd2HeaderPtr;
  347.  
  348. OSErr RetrieveSndHeaderOffset(Handle sndHdl, long *offset) {
  349. /*
  350.     This routine is used to find the offset of a handle to a sound
  351.     into the sound's header. This routine can be used either
  352.     with the older Sound Manager or the newer 3.0 version
  353.     (it calls GetSoundHeaderOffset() if using 3.0, else it
  354.     steps through the muck itself under older versions).
  355.     
  356.     NOTE: Make sure you lock 'sndHdl' before passing it to
  357.     RetrieveSndHeaderOffset...
  358. */
  359.     Ptr myPtr;            // To navigate resource
  360.     long myOffset;        // Offset into resource
  361.     short numSynths;    // Info about resource
  362.     short numCmds;        // Ditto.
  363.     Boolean isDone;        // Are we done yet?
  364.     OSErr myErr;        // Oooh...
  365.  
  366.     if (gSoundMgrVersion >= kEnhancedSoundMgr)
  367.             // Using new sound manager, our work is done for us...
  368. #ifdef __MWERKS__
  369.         return(GetSoundHeaderOffset((SndListHandle)sndHdl, offset));
  370. #else
  371.             // I'm assuming if this is not MetroWerks, it is an older
  372.             // Symantec C++ (non-Universal headers) so here is the
  373.             // workaround. Delete this if you have the Universal
  374.             // headers [yeah I have an older version of SymC++; who
  375.             // wants to pay $200+ for a friggin' upgrade?!?]
  376.         return(20);
  377. #endif
  378.     else {
  379.         // Hmm. Have to do this crap ourselves...
  380.         // Initialize variables
  381.         myOffset = 0;                    // Return 0 if no snd header found
  382.         myPtr = (Ptr)*sndHdl;            // Point to start of rsrc data
  383.         isDone = false;                    // Haven't yet found snd header
  384.         myErr = noErr;
  385.  
  386.             // This thing doesn't work; so I'll just assume it's a
  387.             // Type 1 sound (system) and set the offset as 20. If
  388.             // it's type 2 (hypercard) it won't work, since the offset
  389.             // is 14...
  390.         *offset = 20;
  391.         return(myErr);
  392.  
  393.         // Skip everything before sound commands
  394.         switch(((Snd1HeaderPtr)myPtr)->format) {
  395.  
  396.             // Format 1 'snd ' resource
  397.             case firstSoundFormat:
  398.                 // Skip header start, synth id, etc.
  399.                 numSynths = ((Snd1HeaderPtr)myPtr)->numSynths;
  400.                 myPtr += sizeof(Snd1Header);
  401.                 myPtr += numSynths + sizeof(short) + sizeof(long);
  402.             break;
  403.  
  404.             // Format 2 'snd ' resource
  405.             case secondSoundFormat:
  406.                 myPtr += sizeof(Snd2Header);
  407.             break;
  408.  
  409.             // Unrecognized format
  410.             default:
  411.                 myErr = badFormat;
  412.                 isDone = true;
  413.             break;
  414.         } // END switch
  415.         
  416.         // Find number of commands and move to start of first cmd
  417.         numCmds = *(short*)myPtr;
  418.         myPtr += sizeof(short);
  419.         
  420.         // Search for bufferCmd or soundCmd to obtain sound header
  421.         while ((numCmds >= 1) && !isDone) {
  422.             if ((*(short*)myPtr  == bufferCmd + dataOffsetFlag ||
  423.                 *(short*)myPtr == soundCmd + dataOffsetFlag)) {
  424.                 myOffset = ((SndCommand*)myPtr)->param2;
  425.                 isDone = true;
  426.             }
  427.             else {
  428.                 myPtr += sizeof(SndCommand);
  429.                 numCmds--;
  430.             }
  431.         } // END while
  432.         
  433.         *offset = myOffset;
  434.         return(myErr);
  435.     }
  436. } // END RetrieveSndHeaderOffset
  437.  
  438. // ---------------------------------------------------------------------------
  439.  
  440. SoundHeader *GetSoundHeader(Handle sndHdl) {
  441. /*
  442.     Obtain a pointer to the sound header of a sound resource. Duh!
  443.     
  444.     NOTE: It is up to you to lock the handle to the sound.
  445.     Unlock it ONLY after you a done with the pointer returned
  446.     by GetSoundHeader (if the sound not locked & becomes relocated,
  447.     then your sound header pointer may point to trash!)
  448. */
  449.     long offset;
  450.     OSErr err;
  451.  
  452.     if (sndHdl == nil) return(nil);
  453.     err = RetrieveSndHeaderOffset(sndHdl, &offset);
  454.  
  455.     if (err != noErr)
  456.         return(nil);
  457.     else
  458.         return((SoundHeader*)(StripAddress(*sndHdl) + offset));
  459. } // END GetSoundHeader
  460.  
  461. // ==========================================================================
  462.  
  463. /*
  464.     All the following routines use the callback mechansim:
  465.  
  466.     You play a sound, install a callback, and call a routine
  467.     to check the callback status during your idle loop.
  468.     (use PlayAsynchCallback and CheckSounds).
  469.  
  470.     Before playing, the sound is locked down. Thus it must be
  471.     unlocked eventually. But how do you know when the sound is done?
  472.     There are two ways. One is the callback method. Install a
  473.     callback procedure, and it's called once the sound is done.
  474.     The callback sets a flag, and exits. Your periodically-invoked
  475.     idle procedure then checks this flag, and if set, interprets this
  476.     to mean the sound is done playing; it unlocks and disposes
  477.     of the sound.
  478.     Why can't we dispose of the sound in the callback, instead of
  479.     having to call our checking routine periodically? Because
  480.     callbacks have to stay within the rules of a routine that
  481.     is called during interrupt time: you cannot use any routines
  482.     that may move memory. Thus this semi-roundabout method...
  483.     
  484.     There is one limitation with this method, however. Normally,
  485.     you can queue sounds to be played in a sound channel by calling
  486.     several bufferCmds (or SndPlays for that matter) one after
  487.     another, consecutively, with the same or different sounds.
  488.     You cannot do this using this mechanism! Because when you
  489.     call it, it stores whatever handle to that sound into a private
  490.     data structure, along with the sound channel. Later when the
  491.     sound is done, this sound handle is purged. But if you queue
  492.     another sound to be played (and the current sound isn't done)
  493.     then the new sound handle is stuffed into the private data
  494.     structure, losing our reference to the old sound, the one
  495.     being played. So you have to wait until the sound is done
  496.     (and it's been purged via CheckSounds before playing another
  497.     sound.
  498.     The solution: Try PlayAsynchCallbackPriority(), which takes
  499.     a priority. If the new priority is higher than the priority
  500.     of the sound being played (if any) then this routine takes
  501.     the time to stop the sound and purge the old sound handle
  502.     before playing the new sound...
  503. */
  504.  
  505. OSErr PlayAsynchCallback(Handle sndHdl, short whichChan) {
  506.     SoundHeader *sndDataOffset;
  507.     SndCommand sndCmd;
  508.     long offset;
  509.     OSErr err;
  510.  
  511.     if (sndHdl == nil) return(-1972);
  512.     HLockHi(sndHdl);
  513.  
  514. #ifdef REINIT_CHANNEL
  515.     if (gSoundMgrVersion < kEnhancedSoundMgr) {
  516.         err = DisposeSndChannel(whichChan);
  517.         if (err != noErr) {
  518.             HUnlock(sndHdl); return(err);
  519.         }
  520.         err = CreateSndChannel(whichChan);
  521.         if (err != noErr) {
  522.             HUnlock(sndHdl); return(err);
  523.         }
  524.     }
  525. #endif
  526.  
  527.     err = RetrieveSndHeaderOffset(sndHdl, &offset);
  528.     if (err != noErr) {
  529.         HUnlock(sndHdl);
  530.         return(err);
  531.     }
  532.     sndDataOffset = (SoundHeader*)(StripAddress(*sndHdl) + offset);
  533.  
  534.     sndCmd.cmd = bufferCmd;
  535.     sndCmd.param1 = 0;
  536.     sndCmd.param2 = (long)sndDataOffset;
  537.     err = SndDoCommand(gSndChans[whichChan].chan, &sndCmd, false);
  538.     if (err == noErr) {
  539.         // Install the callback
  540.         gSndChans[whichChan].snd = sndHdl;
  541.         gSndChans[whichChan].priority = 1;
  542.         sndCmd.cmd = callBackCmd;
  543.         sndCmd.param1 = 0;
  544.         sndCmd.param2 = (long)&gSndChans[whichChan];
  545.         err = SndDoCommand(gSndChans[whichChan].chan, &sndCmd, false);
  546.     }
  547.     
  548.     return(err);
  549. } // END PlayAsynchCallback
  550.  
  551. // ---------------------------------------------------------------------------
  552.  
  553. OSErr PlayAsynchCallbackPriority(Handle sndHdl, short whichChan, short priority) {
  554.     SoundHeader *sndDataOffset;
  555.     SndCommand sndCmd;
  556.     long offset;
  557.     OSErr err;
  558.  
  559.     if (sndHdl == nil) return(-1972);
  560.  
  561.     HLockHi(sndHdl);
  562.  
  563.     // First, check priority.
  564.     if (gSndChans[whichChan].priority == kSndIdle ||
  565.         (gSndChans[whichChan].priority) != kSndIdle &&
  566.         (priority >= gSndChans[whichChan].priority)) {
  567.         // OK, new sound has same or higher priority, and a sound
  568.         // is currently playing. Stop the sound...
  569.         if (gSndChans[whichChan].priority != kSndIdle)
  570.             SndChanStop(gSndChans[whichChan].chan);
  571.  
  572. #ifdef REINIT_CHANNEL
  573.             // If we're using the old sound manager (bummer!) we
  574.             // have to allocate a new channel [and destroy the old one]
  575.             // before playing a new sound. Yuck!
  576.         if (gSoundMgrVersion < kEnhancedSoundMgr) {
  577.             err = DisposeSndChannel(whichChan);
  578.             if (err != noErr) {
  579.                 HUnlock(sndHdl); return(err);
  580.             }
  581.             err = CreateSndChannel(whichChan);
  582.             if (err != noErr) {
  583.                 HUnlock(sndHdl); return(err);
  584.             }
  585.         }
  586. #endif
  587.  
  588.         // Time to play the sound...
  589.         err = RetrieveSndHeaderOffset(sndHdl, &offset);
  590.         if (err != noErr) {
  591.             HUnlock(sndHdl);
  592.             return(err);
  593.         }
  594.         sndDataOffset = (SoundHeader*)(StripAddress(*sndHdl) + offset);
  595.     
  596.         sndCmd.cmd = bufferCmd;
  597.         sndCmd.param1 = 0;
  598.         sndCmd.param2 = (long)sndDataOffset;
  599.         // Play the sound
  600.         err = SndDoCommand(gSndChans[whichChan].chan, &sndCmd, false);
  601.  
  602.         // Install the callback
  603.         if (err == noErr) {
  604.             // Install the callback
  605.             gSndChans[whichChan].snd = sndHdl;
  606.             gSndChans[whichChan].priority = priority;
  607.             sndCmd.cmd = callBackCmd;
  608.             sndCmd.param1 = 0;
  609.             sndCmd.param2 = (long)&gSndChans[whichChan];
  610.             err = SndDoCommand(gSndChans[whichChan].chan, &sndCmd, false);
  611.         }
  612.     }
  613.     
  614.     return(err);
  615. } // END PlayAsynchCallbackPriority
  616.  
  617. // ---------------------------------------------------------------------------
  618.  
  619. pascal void SndDoneProc(SndChannelPtr chan, SndCommand *cmd) {
  620. /*
  621.     This is the callback routine. It just sets the
  622.     priority to -1, meaning a sound is done playing.
  623. */
  624.     SndChanInfo *sndChan;
  625.     
  626.     sndChan = (SndChanInfo*)cmd->param2;
  627.     sndChan->priority = kSndDone;
  628. } // END SndDoneProc
  629.  
  630. // ---------------------------------------------------------------------------
  631.  
  632. void CheckSounds(short whichChan, SndCleanupProc cleaner) {
  633. /*
  634.     Call this within your idle loop. It checks the sound channels,
  635.     and disposes of sounds that have been already played, freeing
  636.     up memory.
  637.     
  638.     Pass -1 in argument whichChan to check all sound channels...
  639.  
  640.     You can pass to CheckSounds() the address of a cleanup routine,
  641.     which should do whatever you need it to do after you're done
  642.     with a sound (such as unlocking it and disposing of the sound).
  643.     You can use DefaultSndCleaner(), which unlocks and purges the
  644.     sound. Or pass nil and CheckSounds() will not do anything with
  645.     your sounds (perhaps you want to keep them locked, for example).
  646. */
  647.     short i, max;
  648.     
  649.     if (whichChan == -1) {
  650.         i = 0;
  651.         max = kMaxChans;
  652.     }
  653.     else {
  654.         i = whichChan;
  655.         max = whichChan + 1;
  656.     }
  657.  
  658.     for (i; i < max; i++) {
  659.         if (gSndChans[i].chan != nil && gSndChans[i].priority == kSndDone) {
  660.             // Sound is done playing. Invoke cleaning routine, if any.
  661.             if (cleaner)
  662.                 (*cleaner)(gSndChans[i].snd);
  663.             gSndChans[i].priority = kSndIdle;
  664.         }
  665.     }
  666. } // END CheckSounds
  667.  
  668. // ---------------------------------------------------------------------------
  669.  
  670. void DefaultSndCleaner(Handle snd) {
  671.     HUnlock(snd);
  672.     HPurge(snd);
  673. } // END DefaultSndCleaner